# _*_ encoding: utf-8 _*_
import copy
import logging
import optparse
import sys

try:
    import xml.etree.cElementTree as ET
except ImportError:
    import xml.etree.ElementTree as ET


class DataServer:
    '''
    receive data from data source
    Data source may be file or the remote client
    One can register object to handle received object
    '''

    def __init__(self):
        self.handler = []

    def read(self):
        pass

    def register_handler(self, handle_object):
        if type(handle_object) is DataHandler:
            self.handler.append(handle_object)

    def notify(self, data):
        for o in self.handler:
            o.exeucte(data)


class DataHandler:
    def __init__(self):
        pass

    def execute(self, data):
        pass


class WriteDataHandler(DataHandler):
    def __init__(self, filename):
        self.filename = filename
        self.fp = open(filename, "a")
        self.head = ['id', 'time', 'x', 'y', 'angle', 'type', 'speed', 'pos', 'lane']

    def __del__(self):
        self.fp.close()

    def execute(self, data):
        elems = []
        for key in self.head:
            if key in data:
                elems.append(data[key])
            else:
                elems.append("")
        lines = ",".join(elems) + "\n"
        self.fp.write(lines)


class IndexHandler(DataHandler):
    __edge_info = dict()
    BEGIN_TIME = -1
    road_net_length = 0

    @classmethod
    def load_network(cls, filename):
        tree = ET.ElementTree(file=filename)
        for child in tree.getroot():
            if child.tag == 'edge':
                edge_id = child.attrib['id']
                edge_info = {}
                if 'type' in child.attrib:
                    edge_info.setdefault('type', child.attrib['type'])
                if 'priority' in child.attrib:
                    edge_info.setdefault('priority', int(child.attrib['priority']))

                edge_info.setdefault('speed', float(child[0].attrib['speed']))
                edge_info.setdefault('length', float(child[0].attrib['length']))
                cls.__edge_info.setdefault(edge_id, edge_info)
                cls.road_net_length += float(child[0].attrib['length'])

    def __init__(self, interval, write_file, velocity_file=None):
        self.data_set = {}
        self.interval = interval
        self.begin_time = self.BEGIN_TIME
        self.index = []
        self.fp = open(write_file, "wb")
        self.velocity_file = velocity_file

    def __del__(self):
        self.fp.close()

    def store(self, data, clear=False):
        if clear:
            del self.data_set
            self.data_set = {}
        self.data_set.setdefault(data['id'], [])
        # self.data_set[data['id']].append(data)
        if data['lane'][0] == ':':
            elems = data['lane'].split('_')
            data['lane'] = "_".join(elems[:-1])
        else:
            data['lane'] = data['lane'].split('_')[0]
        if len(self.data_set[data['id']]) >= 2:
            start = self.data_set[data['id']][-2]
            if start['lane'] == data['lane']:
                self.data_set[data['id']][-1] = data
            else:
                self.data_set[data['id']].append(data)
        else:
            self.data_set[data['id']].append(data)

    def execute(self, data):
        res = -1
        if 'time' not in data :
            print 'reject data: data format error!'
            exit(1)
        if self.begin_time == self.BEGIN_TIME or data['time'] - self.begin_time <= self.interval:
            self.store(data)
            if self.begin_time == self.BEGIN_TIME:
                self.begin_time = data['time']
        elif data['time'] - self.begin_time > self.interval:
            res = self.calculate_index()
            self.store(data, clear=True)
            self.begin_time = data['time']
        return res

    def write_velocity(self, edges):
        with open(self.velocity_file, "a") as fv:
            buf = []
            for key, item in edges.items():
                buf.append("{0},{1},{2}\n".format(self.begin_time, key, item[0]/item[1]))
            fv.writelines(buf)
            del buf

    def aggregate_car_information(self):
        edges = {}
        for vid, dataset in self.data_set.items():
            l = len(dataset)
            i = 0
            while i < l:
                c_eid = dataset[i]['lane']
                n_eid = None
                v = float(dataset[i]['speed'])
                if i + 1 < l:
                    n_eid = dataset[i + 1]['lane']
                if c_eid == n_eid:
                    mileage = float(dataset[i + 1]['pos']) - float(dataset[i]['pos'])
                    t = float(dataset[i + 1]['time']) - float(dataset[i]['time'])
                    v = mileage * 1.0 / t
                    i += 2
                else:
                    i += 1
                edges.setdefault(c_eid, [0, 0])
                edges[c_eid][0] += v
                edges[c_eid][1] += 1
        if self.velocity_file is not None:
            self.write_velocity(edges)
        return edges

    def buffer(self, lines):
        self.fp.writelines(lines)

    def map(self, index):
        index *= 100
        if index <= 5:
            index = index / 5 * 2
        elif index <= 8:
            index = 2 + (index - 5) / 3 * 2
        elif index <= 11:
            index = 4 + (index - 8) / 3 * 2
        elif index <= 14:
            index = 6 + (index - 11) / 3 * 2
        elif index <= 24:
            index = 8 + (index - 14) / 10 * 2
        else:
            index = 10
        return index

    def calculate_index(self):
        # edges : {edge_id_1: [sum_speed, number_vehicle], ...}
        edges = self.aggregate_car_information()
        # types : {types_id: {'vkt': , 'struck': }}
        types = {}
        for eid, items in edges.items():
            avg = items[0] / items[1]  # average speed
            if 'type' in IndexHandler.__edge_info[eid]:  # map to road type
                tid = IndexHandler.__edge_info[eid]['type']
                types.setdefault(tid, {})
                length = IndexHandler.__edge_info[eid]['length']
                # calculate the vkt of each road type
                types[tid].setdefault('vkt', 0)
                types[tid]['vkt'] += length * items[1]
                # whether the edge is struck
                types[tid].setdefault('struck', 0)
                if avg < IndexHandler.__edge_info[eid]['speed'] * 0.35:
                    types[tid]['struck'] += length
        sum = 0
        for tid, item in types.items():
            sum += item['vkt']

        index = 0
        for tid, item in types.items():
            index += item['struck'] / IndexHandler.road_net_length * item['vkt'] / sum
        proportion = index
        index = self.map(index)
        logging.info("time:{0},proportion:{1},index:{2}".format(self.begin_time, proportion*100, index))
        self.buffer("{0},{1},{2}\n".format(self.begin_time, proportion, index))

        del edges
        del types

        return index

    def write_result(self, filename):
        fp = open(filename, "wb")
        for item in self.index:
            fp.write("{0},{1}\n".format(item[0], item[1]))
        fp.close()


def load_xml(filename, handler):
    if not isinstance(handler, DataHandler):
        print "wrong handler!"
        exit(-1)
    current_time = 0
    for event, elem in ET.iterparse(filename):
        if event == 'end':
            if elem.tag == 'timestep':
                current_time = float(elem.attrib['time'])
            elif elem.tag == 'vehicle':
                veh = copy.deepcopy(elem.attrib)
                veh.setdefault('time', current_time)
                handler.execute(veh)
    print("load xml finished!")


def load_gps_csv(filename, handler):
    if not isinstance(handler, DataHandler):
        print 'wrong handler!'
        exit(-1)
    header = ['id', 'time', 'x', 'y', 'angle', 'type', 'speed', 'pos', 'lane']
    with open(filename, "rb") as fp:
        for line in fp:
            elems = line.strip().split(',')
            data = {}
            for i, key in enumerate(header):
                data.setdefault(key, elems[i])
                if key == 'time':
                    data[key] = int(float(elems[i]))
                if key == 'x' or key == 'y' or key == 'speed' or key == 'pos':
                    data[key] = float(elems[i])
            handler.execute(data)
    print("parse file finished!")


def get_options(args=None):
    optParser = optparse.OptionParser()
    optParser.add_option("-n", "--net-file", dest="netfile", help="define the net file (mandatory)")
    optParser.add_option("-l", "--logs", dest="logfile", help="define the output log file")
    optParser.add_option("-x", "--xml", dest="xml_gps", help="define the input gps trace file in xml format")
    optParser.add_option("-c", "--csv", dest="csv_gps", help="define the input gps trace file in csv format")
    optParser.add_option("-o", "--output-file", dest="outputfile", help="define the output file")
    optParser.add_option("-i", "--interval", type="int", dest="interval", help="define the interval (minute) of statistics")
    optParser.add_option("-v", "--velocity", dest="velocityfile", help="output velocity in different period")
    (options, args) = optParser.parse_args(args=args)

    if not options.netfile:
        optParser.print_help()
        sys.exit()

    if not options.xml_gps and not options.csv_gps:
        optParser.print_help()
        sys.exit()

    if not options.logfile:
        optParser.print_help()
        sys.exit()

    if not options.outputfile:
        optParser.print_help()
        sys.exit()
    return options


def main(options):

    logging.basicConfig(level=logging.DEBUG, format="%(message)s", filename=options.logfile, filemode="w")
    IndexHandler.load_network(options.netfile)

    interval = 5
    if options.interval:
        interval = options.interval
    handler = IndexHandler(interval * 60, options.outputfile, options.velocityfile)
    if options.xml_gps:
        load_xml(options.xml_gps, handler)
    elif options.csv_gps:
        load_gps_csv(options.csv_gps, handler)
    return True

if __name__ == '__main__':
    if not main(get_options()):
        sys.exit(1)
